!
!	setup.s		(C) 1991 Linus Torvalds
!
! setup.s is responsible for getting the system data from the BIOS,
! and putting them into the appropriate places in system memory.
! both setup.s and system has been loaded by the bootblock.
!
! This code asks the bios for memory/disk/other parameters, and
! puts them in a "safe" place: 0x90000-0x901FF, ie where the
! boot-block used to be. It is then up to the protected mode
! system to read them from there before the area is overwritten
! for buffer-blocks.
!

! NOTE! These had better be the same as in bootsect.s!

INITSEG  = 0x9000	! we move boot here - out of the way
SYSSEG   = 0x1000	! system loaded at 0x10000 (65536).
SETUPSEG = 0x9020	! this is the current segment

.globl begtext, begdata, begbss, endtext, enddata, endbss
.text
begtext:
.data
begdata:
.bss
begbss:
.text

entry start
start:

! ok, the read went well so we get current cursor position and save it for
! posterity.

	mov	ax,#INITSEG	! this is done in bootsect already, but... ! 这是将ds设置成0x9000，bootsect中已经设置过了，Linus又设置了一次
	mov	ds,ax
	mov	ah,#0x03	! read cursor pos
	xor	bh,bh
	int	0x10		! save it in known place, con_init fetches
	mov	[0],dx		! it from 0x90000.
! 上面的含义是将光标位置信息保存在0x90000处
! Get memory size (extended mem, kB)

	mov	ah,#0x88
	int	0x15
	mov	[2],ax ! 将扩展内存数值存在0x90002处
! 调用0x15 扩展内存大小。返回：ax = 从0x100000处开始的扩展内存大小
! Get video-card data:

	mov	ah,#0x0f
	int	0x10
	mov	[4],bx		! bh = display page
	mov	[6],ax		! al = video mode, ah = window width

! check for EGA/VGA and some config parameters

	mov	ah,#0x12
	mov	bl,#0x10
	int	0x10
	mov	[8],ax
	mov	[10],bx
	mov	[12],cx

! Get hd0 data

	mov	ax,#0x0000
	mov	ds,ax
	lds	si,[4*0x41]
	mov	ax,#INITSEG
	mov	es,ax
	mov	di,#0x0080
	mov	cx,#0x10
	rep
	movsb

! Get hd1 data

	mov	ax,#0x0000
	mov	ds,ax
	lds	si,[4*0x46]
	mov	ax,#INITSEG
	mov	es,ax
	mov	di,#0x0090
	mov	cx,#0x10
	rep
	movsb

! Check that there IS a hd1 :-)

	mov	ax,#0x01500
	mov	dl,#0x81
	int	0x13
	jc	no_disk1
	cmp	ah,#3
	je	is_disk1
no_disk1:
	mov	ax,#INITSEG
	mov	es,ax
	mov	di,#0x0090
	mov	cx,#0x10
	mov	ax,#0x00
	rep
	stosb
is_disk1:

! now we want to move to protected mode ...

	cli			! no interrupts allowed ! 关闭中断

! first we move the system to it's rightful place

	mov	ax,#0x0000
	cld			! 'direction'=0, movs moves forward
do_move:
	mov	es,ax		! destination segment ! es:di 目的地址，初始为0x0000:0x0
	add	ax,#0x1000
	cmp	ax,#0x9000
	jz	end_move
	mov	ds,ax		! source segment ! ds:si 源地址，初始为0x1000:0x0
	sub	di,di
	sub	si,si
	mov 	cx,#0x8000 !总计移动0x8000个字(64KB)
	rep
	movsw
	jmp	do_move

! then we load the segment descriptors

end_move:
	mov	ax,#SETUPSEG	! right, forgot this at first. didn't work :-)
	mov	ds,ax
	lidt	idt_48		! load idt with 0,0
	lgdt	gdt_48		! load gdt with whatever appropriate

! that was painless, now we enable A20

	call	empty_8042 ! 等待输入缓冲器空。只有输入缓冲器为空时，才能对其写命令。8042是英特尔键盘控制器。
	mov	al,#0xD1		! command write
	out	#0x64,al ! 向0x64 端口写入0xd1
	call	empty_8042 ! 等待输入缓冲器空，看命令是否被接受
	mov	al,#0xDF		! A20 on ! 选通 A20 地址线的参数
	out	#0x60,al
	call	empty_8042 ! 输入缓冲器为空，则表示A20线已经选通

! well, that went ok, I hope. Now we have to reprogram the interrupts :-(
! we put them right after the intel-reserved hardware interrupts, at
! int 0x20-0x2F. There they won't mess up anything. Sadly IBM really
! messed this up with the original PC, and they haven't been able to
! rectify it afterwards. Thus the bios puts interrupts at 0x08-0x0f,
! which is used for the internal hardware interrupts as well. We just
! have to reprogram the 8259's, and it isn't fun.
! 现在我们必须重新对中断进行编程 :-( 我们将它们放在正好处于Intel保留的硬件中断后面，即int 0x20--0x2F。在那里它们不会引起冲突。不幸的是IBM在原PC机中搞糟了，以后也没有纠正过来。PC机BIOS把中断放在了0x08--0x0f，这些中断也被用于内部硬件中断。所以我们就必须重新对8259中断控制器进行编程
	mov	al,#0x11		! initialization sequence !输出值0x11表示初始化命令开始，它是ICW1命令字，表示边沿触发、多片8259级连、最后要发送ICW4命令字。
	out	#0x20,al		! send it to 8259A-1 ! 8259芯片主片端口是0x20-0x21，从片端口是0xA0-0xA1。
	.word	0x00eb,0x00eb		! jmp $+2, jmp $+2 ! 两条跳转指令，跳转到下一指令，起到延时作用
	out	#0xA0,al		! and to 8259A-2 ! 8259A从芯片
	.word	0x00eb,0x00eb
	mov	al,#0x20		! start of hardware int's (0x20) ! 送主芯片ICW2命令字，起始中断号是0x20
	out	#0x21,al
	.word	0x00eb,0x00eb
	mov	al,#0x28		! start of hardware int's 2 (0x28) ! 送从芯片的ICW2命令字，起始中断号是0x28
	out	#0xA1,al
	.word	0x00eb,0x00eb
	mov	al,#0x04		! 8259-1 is master ! 送主芯片ICW3命令字，主芯片的IR2连从芯片INT
	out	#0x21,al
	.word	0x00eb,0x00eb
	mov	al,#0x02		! 8259-2 is slave ! 送从芯片ICW3命令字，表示从芯片INT连到主芯片IR2引脚上
	out	#0xA1,al
	.word	0x00eb,0x00eb
	mov	al,#0x01		! 8086 mode for both
	out	#0x21,al ! 送主芯片ICW4命令字，设置成8086模式；普通EOI模式
	.word	0x00eb,0x00eb
	out	#0xA1,al ! 送从芯片ICW4命令字，设置成8086模式
	.word	0x00eb,0x00eb
	mov	al,#0xFF		! mask off all interrupts for now
	out	#0x21,al ! 屏蔽主芯片一切中断请求
	.word	0x00eb,0x00eb
	out	#0xA1,al! 屏蔽从芯片一切中断请求

! well, that certainly wasn't fun :-(. Hopefully it works, and we don't
! need no steenking BIOS anyway (except for the initial loading :-).
! The BIOS-routine wants lots of unnecessary data, and it's less
! "interesting" anyway. This is how REAL programmers do it.
!
! Well, now's the time to actually move into protected mode. To make
! things as simple as possible, we do no register set-up or anything,
! we let the gnu-compiled 32-bit programs do that. We just jump to
! absolute address 0x00000, in 32-bit protected mode.

	mov	ax,#0x0001	! protected mode (PE) bit
	lmsw	ax		! This is it! ! 加载机器状态字CR0，使其置为0x0001，这样CPU将工作在保护模式。并且运行在特权级0中，即当前特权级CPL=0。此时段寄存器仍然指向与实地址模式中相同的线性地址处（在实地址模式下线性地址与物理内存地址相同）。lmsw指令仅用于兼容以前的286 CPU。
	jmpi	0,8		! jmp offset 0 of segment 8 (cs) ! 段间跳转指令以用于刷新CPU当前指令队列。因为CPU是在执行一条指令之前就已从内存读取该指令并对其进行解码。然而在进入保护模式以后那些属于实模式的预先取得的指令信息就变得不再有效。而一条段间跳转指令就会刷新CPU的当前指令队列，即丢弃这些无效信息。另外，在Intel公司的手册上建议80386或以上CPU应该使用指令“mov cr0,ax”切换到保护模式。此时已经进入保护模式，0,8就是段选择符
! 我们已经将system模块移动到0x00000开始的地方，所以上句中的偏移地址是0。而段值8已经是保护模式下的段选择符了，用于选择描述符表和描述符表项以及所要求的特权级。段选择符长度为16位（2字节）；位0-1表示请求的特权级0--3，但Linux操作系统只用到两级：0级（内核级）和3级（用户级）；位2用于选择全局描述符表（0）还是局部描述符表(1)；位3-15是描述符表项的索引，指出选择第几项描述符。所以段选择符8（0b0000,0000,0000,1000）表示请求特权级0、使用全局描述符表GDT中第2个段描述符项，该项指出代码的基地址是0（参见571行），因此这里的跳转指令就会去执行system中的代码。
! This routine checks that the keyboard command queue is empty
! No timeout is used - if this hangs there is something wrong with
! the machine, and we probably couldn't proceed anyway.
empty_8042:
	.word	0x00eb,0x00eb
	in	al,#0x64	! 8042 status port
	test	al,#2		! is input buffer full?
	jnz	empty_8042	! yes - loop
	ret

gdt:
	.word	0,0,0,0		! dummy 第一个描述符，不用
! 这里gdt表中的偏移量为0x08（因为在内存中这里第一个字节就是gdt中的第八个字节）,当加载代码段寄存器时，使用的就是这个值
	.word	0x07FF		! 8Mb - limit=2047 (2048*4096=8MB)
	.word	0x0000		! base address=0
	.word	0x9A00		! code read/exec
	.word	0x00C0		! granularity=4096, 386
! 这里gdt表中的偏移量为0x10，当加载数据段寄存器时，使用的就是这个值
	.word	0x07FF		! 8Mb - limit=2047 (2048*4096=8Mb)
	.word	0x0000		! base address=0
	.word	0x9200		! data read/write
	.word	0x00C0		! granularity=4096, 386 颗粒度为1，表示段限长单位是4KB（4096B）

idt_48:
	.word	0			! idt limit=0 idt表的限长
	.word	0,0			! idt base=0L 

gdt_48:
	.word	0x800		! gdt limit=2048, 256 GDT entries
	.word	512+gdt,0x9	! gdt base = 0X9xxxx
! 512+gdt,0x9 即是 (0x0009 << 16) + (0x0200 + gdt) = 0x90200 + gdt, 512 + gdt = 0x0314(查看二进制文件)
.text
endtext:
.data
enddata:
.bss
endbss:
